#####
#
# TAGGING
#

We're working on a machine with 64 bit words that address bytes.

* 64 bits = 2^3 = 8 bytes

If we ensure our pointers are 8 byte aligned then their
addresses all end in 000. We can use these 3 bits as a tag.

pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppttt

not every object is a pointer though, we have immediates:

(A) bool, nil, symbol, char
(B) number

(We don't need the symbol object to be a pointer, an index into the
symbol table is fine since displaying symbol names isn't a speed
priority.)

In the case of immediates we can smush several into one ttt tag
and use some more bits ss to distinguish them:

iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiissttt

We also make vectors a special type of a record, to save a tag.

* The special 'all zeros' value of #f can be used with jnz.
* We can save the 'nil' value in a register for fast comparison.
* Char is large enough to hold a unicode code point.

type    obj                       data
--------------------------------------
bool    #f             .....0[00][000]
bool    #t             .....1[00][000]
nil     nil            .....0[01][000]
symbol       [--32 bits--]...[10][000]
char         [--32 bits--]...[11][000]
number     [-------61 bits------][001]
cons                            *[010]
closure                         *[011]
string                          *[100]
vector + record                 *[101]
io port                          [110]
gc-redirect marker              *[111]



#####
#
# LAYOUT
#

All objects will point into the heap except strings may be stored
elsewhere (e.g. constant strings stored in the data section).

ptr           heap
-------------------------------------------------------------
cons     -->  [ car     ][ cdr     ]
closure  -->  [ label   ][ length  ][ env1    ][ env2    ]...
string   -->  <string-data>
vec/rec  -->  [ tag/len ][ field-1 ][ field 2 ][ field-3 ]...
gc-mark  -->  <an objects new place in the other-heap>



#####
#
# CALLING CONVENTION
#

The calling convention is like this:

- Caller enter:
push base-pointer
set base-pointer to stack-pointer
push arg-1
push arg-2
...
push arg-n
call (pushes return address)

this sets the stack up like:

[ret addr][saved bp][arg-1][arg-2]..[arg-n]
                      ^ new bp

- Callee enter:
increase the stack size to make space for its own temps

- Callee exit:

we need to throw away all the temps and args
restore the previous base pointer
and jump to the return address

mov rsp, rbp
pop rbp
ret

- Caller exit:
The caller has no resposibility.
All the args it pushed on the stack have been consumed, base pointer restored.



#####
#
# STACK
#

The stack layout needs to support the following:

* The garbage collector needs to scan through to as a gc root.
  For this to be possible it has to be able to skip past non scheme objects like labels.
* We need to be able to produce stack traces based on the labels.
* Support variable number of arguments to function calls


. previous stack .
: frame          :
+----------------+
| return address |
+----------------+
| saved base ptr |
+----------------+
| arg 1          | <- base pointer
+----------------+
| arg 2          |
+----------------+
:                :
:                :
+----------------+
| arg n          |
+----------------+
| tmp 1          |
+----------------+
| tmp 2          |
+----------------+
:                :
:                :
+----------------+
| tmp n          |
+----------------+
: scheme objects : (this will usually just
: saved by the   :  be the env register)
: callee         :
+----------------+
        .          <-- stack pointer

args will be referenced relative to base pointer
temporaries will be referenced relative to stack pointer

this allows us to support variadic functions!

in the case of a variadic function the callee must cons up the rest list. The reason for this is it allows fast function calls in the non-variadic case.

